// pendulum.js
// 单摆物理仿真模块

/**
 * Pendulum 模块负责单摆参数、仿真、绘制、实验控制等
 */
export const Pendulum = {
  // 单摆参数与状态
  params: {
    physicalLength: 100, // 摆长，单位cm
    displayScale: 2.5,   // 视觉增强比例
    angle: 5,            // 初始角度，单位度
    pivotX: 700,
    pivotY: 180,
    bobRadius: 24,
    angularVelocity: 0,
    angularAcceleration: 0,
    time: 0,
    running: false,
    periodCount: 0,
    lastPeriodTime: 0,
    periods: [],
    periodTiming: false,
    startTime: 0,
    maxAngularVelocity: 0,
    initialEnergy: 0,
    currentEnergy: 0,
    lastHiddenTime: null
  },
  // 画布与控件引用
  canvas: null,
  ctx: null,
  lengthSlider: null,
  angleSlider: null,
  startBtn: null,
  resetBtn: null,
  lengthValue: null,
  angleValue: null,
  timer: null,
  // 动画数据
  animationAngleHistory: [],
  animationPositionHistory: [],
  animationStartTime: 0,
  lastDrawTime: 0,
  DRAW_INTERVAL: 16,
  // 摄像头同步相关变量
  isSyncingFromCamera: false,
  lastUpdateTime: 0,
  MIN_UPDATE_INTERVAL: 8,
  angleHistory: [],
  lengthHistory: [],
  timeHistory: [],
  HISTORY_SIZE: 5,
  frameCount: 0,
  lastFpsTime: 0,
  currentFps: 0,
  syncLatency: 0,
  // 添加状态标记，防止多个请求重叠
  isFetchingCameraData: false,
  cameraAngle: 0, // 新增属性，保存摄像头角度
  displayPhysicalLength: 100, // 新增属性，动画显示用摆长

  /**
   * 初始化单摆模块，绑定事件、初始化画布
   */
  init() {
    // 获取DOM元素
    this.canvas = document.getElementById('pendulumCanvas');
    this.ctx = this.canvas.getContext('2d');
    this.lengthSlider = document.getElementById('length');
    this.angleSlider = document.getElementById('angle');
    this.startBtn = document.getElementById('startBtn');
    this.resetBtn = document.getElementById('resetBtn');
    this.lengthValue = document.getElementById('lengthValue');
    this.angleValue = document.getElementById('angleValue');
    this.timer = document.getElementById('timer');

    // 初始化状态指示器
    const runStatus = document.getElementById('runStatus');
    const statusText = runStatus?.querySelector('.status-text');
    if (runStatus && statusText) {
      runStatus.classList.remove('running');
      runStatus.classList.add('not-running');
      statusText.textContent = '未运行';
    }

    // 初始化性能统计
    this.frameCount = 0;
    this.lastFpsTime = performance.now();
    this.currentFps = 0;
    this.syncLatency = 0;
    
    // 启动性能统计更新定时器
    setInterval(() => {
      if (!this.params.running) {
        this.updatePerformanceStats();
      }
    }, 1000);

    // 绑定滑块事件
    this.lengthSlider.addEventListener('input', () => {
      this.params.physicalLength = parseFloat(this.lengthSlider.value);
      this.updateSliders();
      // 不再实时更新动画摆长
      // if (!this.params.running) {
      //   this.draw();
      // }
      // 只有在非同步状态下才发送到后端
      if (!window.isSyncingFromCamera) {
        window.sendKey && window.sendKey('2', {m: this.params.physicalLength});
      }
    });
    this.angleSlider.addEventListener('input', () => {
      this.params.angle = parseFloat(this.angleSlider.value);
      this.updateSliders();
      // 实时更新动画角度
      if (!this.params.running) this.draw();
      // 只有在非同步状态下才发送到后端
      if (!window.isSyncingFromCamera) {
        window.sendKey && window.sendKey('a', {angle: this.params.angle});
      }
    });
    // 绑定按钮事件
    console.log('准备绑定开始实验按钮事件');
    this.startBtn.addEventListener('click', () => {
      console.log('开始实验按钮被点击');
      this.toggleStart();
    });
    this.resetBtn.addEventListener('click', () => this.reset());
    // 初始化画布
    this.setupCanvas();
    window.addEventListener('resize', () => this.setupCanvas());
    // 初始化实验
    this.reset();
    // 挂载到全局，供其他模块访问
    window.pendulum = this.params;
    // 按钮与信号映射
    const btnMap = [
        {id: 'btnUpM', down: 'w', up: 'a'},
        {id: 'btnDownM', down: 's', up: 'a'},
        {id: 'btnUpU', down: '3', up: '5'},
        {id: 'btnDownU', down: '4', up: '5'},
        {id: 'btnUpN', down: '6', up: '8'},
        {id: 'btnDownN', down: '7', up: '8'},
        {id: 'btnClear', down: '0', up: null}
    ];
    // 定义全局sendKey函数
    window.sendKey = function(key, extra={}) {
        console.log('调用sendKey函数:', key, extra);
        fetch('http://127.0.0.1:5000/pendulum_key', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(Object.assign({key}, extra))
        })
        .then(response => {
            console.log('sendKey请求成功:', response.status);
        })
        .catch(error => {
            console.error('sendKey请求失败:', error);
        });
    };
    
    btnMap.forEach(({id, down, up}) => {
        const btn = document.getElementById(id);
        if (!btn) return;
        if (down) btn.addEventListener('mousedown', () => window.sendKey(down));
        if (up) btn.addEventListener('mouseup', () => window.sendKey(up));
    });
    // 文本框输入n，点击发送
    const btnSendN = document.getElementById('btnSendN');
    if (btnSendN) btnSendN.addEventListener('click', () => {
        const n = parseInt(document.getElementById('inputN').value) || 1;
        window.sendKey('1', {n});
    });
    // 文本框输入m，点击发送
    const btnSendM = document.getElementById('btnSendM');
    if (btnSendM) btnSendM.addEventListener('click', () => {
        const m = parseFloat(document.getElementById('inputM').value) || 0;
        if (m > 0) {
            // 发送m值到后端
            window.sendKey('2', {m});
            // 不再同步更新前端界面的摆长
            // this.params.physicalLength = m;
            // if (this.lengthSlider) {
            //     this.lengthSlider.value = m;
            // }
            // this.updateSliders();
            // if (!this.params.running) {
            //     this.draw();
            // }
            console.log('摆长已发送到后端:', m);
        }
    });
    // 清除按钮点击时清空输入框
    const btnClear = document.getElementById('btnClear');
    if (btnClear) btnClear.addEventListener('click', () => {
        document.getElementById('inputN').value = '';
        document.getElementById('inputM').value = '';
    });
    
    
    // 禁止输入框聚焦时触发快捷键
    const inputN = document.getElementById('inputN');
    const inputM = document.getElementById('inputM');
    window.inputActive = false;
    if (inputN) {
      inputN.addEventListener('focus', () => { window.inputActive = true; });
      inputN.addEventListener('blur', () => { window.inputActive = false; });
    }
    if (inputM) {
      inputM.addEventListener('focus', () => { window.inputActive = true; });
      inputM.addEventListener('blur', () => { window.inputActive = false; });
    }
    // 全局快捷键监听时判断
    document.addEventListener('keydown', function(e) {
      if (window.inputActive) return;
      // ...此处可放快捷键处理逻辑...
    });



    // 点击"d0"按钮发送d0
    const d0Btn = document.getElementById('d0Btn');
    console.log('找到d0按钮:', !!d0Btn);
    if (d0Btn) {
      // 移除之前可能存在的事件监听器
      d0Btn.removeEventListener('click', d0ClickHandler);
      
      // 使用命名函数便于后续调试
      function d0ClickHandler() {
        console.log('d0按钮点击，准备发送d0信号');
        window.sendKey('d0');
        console.log('d0信号已发送');
        
        // 添加点击反馈
        d0Btn.style.transform = 'scale(0.95)';
        setTimeout(() => {
            d0Btn.style.transform = 'scale(1)';
        }, 150);
      }
      
      // 添加新的事件监听器
      d0Btn.addEventListener('click', d0ClickHandler);
      console.log('d0按钮事件监听器已添加');
    }

    // 点击"d1"按钮发送d1
    const d1Btn = document.getElementById('d1Btn');
    if (d1Btn) {
      d1Btn.addEventListener('click', () => {
        window.sendKey('d1');
        // 添加点击反馈
        d1Btn.style.transform = 'scale(0.95)';
        setTimeout(() => {
            d1Btn.style.transform = 'scale(1)';
        }, 150);
      });
    }

    // 添加获取摆长数据的按钮
    const getPendulumLengthBtn = document.getElementById('getPendulumLengthBtn');
    if (getPendulumLengthBtn) {
      getPendulumLengthBtn.addEventListener('click', () => {
        console.log('获取摆长数据按钮被点击');
        
        // 添加点击反馈
        getPendulumLengthBtn.style.transform = 'scale(0.95)';
        setTimeout(() => {
          getPendulumLengthBtn.style.transform = 'scale(1)';
        }, 150);
        
        // 调用获取摆长数据的函数
        this.fetchPendulumLength();
      });
      console.log('获取摆长数据按钮事件监听器已添加');
    }

    // 添加获取角度数据的按钮
    const getPendulumAngleBtn = document.getElementById('getPendulumAngleBtn');
    if (getPendulumAngleBtn) {
      getPendulumAngleBtn.addEventListener('click', () => {
        console.log('获取角度数据按钮被点击');
        
        // 添加点击反馈
        getPendulumAngleBtn.style.transform = 'scale(0.95)';
        setTimeout(() => {
          getPendulumAngleBtn.style.transform = 'scale(1)';
        }, 150);
        
        // 调用获取角度数据的函数
        this.fetchPendulumAngle();
      });
      console.log('获取角度数据按钮事件监听器已添加');
    }

    // 定时从后端获取摄像头角度并同步（只同步角度）
    setInterval(() => {
      this.fetchAndSyncFromCamera();
    }, 100); // 降低频率，从16ms改为100ms

    // 不再自动轮询获取摆长数据
    // setInterval(() => {
    //   this.fetchPendulumLength();
    // }, 500);

    // 定时刷新曲线
    // setInterval(() => {
    //   this.updateAngleChart();
    //   this.updateXYChart();
    // }, 16);
  },

  /**
   * 从后端获取单片机发送的摆长数据
   */
  fetchPendulumLength() {
    if (!this.params) return;
    
    // 显示获取状态
    const lengthValue = document.getElementById('lengthValue');
    if (lengthValue) {
      lengthValue.textContent = this.params.physicalLength.toFixed(1) + ' cm (正在获取...)';
    }
    
    fetch('http://127.0.0.1:5000/get_pendulum_length')
      .then(r => {
        if (!r.ok) {
          throw new Error(`HTTP错误: ${r.status}`);
        }
        return r.json();
      })
      .then(data => {
        // 如果后端返回了有效的摆长数据
        if (data && typeof data.length === 'number' && data.length > 0) {
          console.log('收到单片机摆长数据:', data.length);
          
          // 更新摆长参数
          this.params.physicalLength = data.length;
          this.displayPhysicalLength = data.length; // 只在这里更新动画用摆长
          
          // 更新摆长滑块的值
          if (this.lengthSlider) {
            this.lengthSlider.value = this.params.physicalLength;
          }
          
          // 更新显示
          this.updateSliders();
          
          // 如果不在运行状态，重绘动画
          if (!this.params.running) {
            this.draw();
          }
        } else {
          console.warn('获取摆长数据: 返回数据格式不正确', data);
          if (lengthValue) {
            lengthValue.textContent = this.params.physicalLength.toFixed(1) + ' cm (单片机控制)';
          }
        }
      })
      .catch(error => {
        console.error('获取摆长数据失败:', error);
        // 显示错误状态
        if (lengthValue) {
          lengthValue.textContent = this.params.physicalLength.toFixed(1) + ' cm (获取失败)';
          // 3秒后恢复显示
          setTimeout(() => {
            if (lengthValue) {
              lengthValue.textContent = this.params.physicalLength.toFixed(1) + ' cm (单片机控制)';
            }
          }, 3000);
        }
      });
  },

  /**
   * 从后端获取单片机发送的角度数据
   */
  fetchPendulumAngle() {
    if (!this.params) return;
    
    // 显示获取状态
    const angleValue = document.getElementById('angleValue');
    if (angleValue) {
      angleValue.textContent = this.params.angle.toFixed(1) + '° (正在获取...)';
    }
    
    fetch('http://127.0.0.1:5000/get_pendulum_angle')
      .then(r => {
        if (!r.ok) {
          throw new Error(`HTTP错误: ${r.status}`);
        }
        return r.json();
      })
      .then(data => {
        // 如果后端返回了有效的角度数据
        if (data && typeof data.angle === 'number') {
          console.log('收到单片机角度数据:', data.angle);
          
          // 更新角度参数
          this.params.angle = data.angle;
          
          // 更新角度滑块的值
          if (this.angleSlider) {
            this.angleSlider.value = this.params.angle;
          }
          
          // 更新显示
          this.updateSliders();
          
          // 如果不在运行状态，重绘动画
          if (!this.params.running) {
            this.draw();
          }
        } else {
          console.warn('获取角度数据: 返回数据格式不正确', data);
          if (angleValue) {
            angleValue.textContent = this.params.angle.toFixed(1) + '° (单片机控制)';
          }
        }
      })
      .catch(error => {
        console.error('获取角度数据失败:', error);
        // 显示错误状态
        if (angleValue) {
          angleValue.textContent = this.params.angle.toFixed(1) + '° (获取失败)';
          // 3秒后恢复显示
          setTimeout(() => {
            if (angleValue) {
              angleValue.textContent = this.params.angle.toFixed(1) + '° (单片机控制)';
            }
          }, 3000);
        }
      });
  },

  /**
   * 设置画布尺寸并重绘
   */
  setupCanvas() {
    const container = this.canvas.parentElement;
    this.canvas.width = container.clientWidth * 1.5;
    this.canvas.height = container.clientHeight * 1.5;
    this.params.pivotX = this.canvas.width / 2;
    this.draw();
  },

  /**
   * 更新滑块显示
   */
  updateSliders() {
    this.lengthValue.textContent = this.params.physicalLength.toFixed(1) + ' cm (单片机控制)';
    // 角度显示逻辑
    if (!this.params.running) {
      // 未运行时显示两个角度
      const cameraAngleText = typeof this.cameraAngle === 'number' ? this.cameraAngle.toFixed(1) : '0.0';
      const sliderAngleText = parseFloat(this.angleSlider.value).toFixed(1); // 使用滑块当前值
      this.angleValue.textContent = `摄像头: ${cameraAngleText}° | 滑块: ${sliderAngleText}°`;
    } else {
      // 运行时只显示滑块角度
      this.angleValue.textContent = parseFloat(this.angleSlider.value).toFixed(1) + '°';
    }
  },

  /**
   * 启动/暂停实验
   */
  toggleStart() {
    const runStatus = document.getElementById('runStatus');
    const statusText = runStatus?.querySelector('.status-text');
    const syncStatus2 = document.getElementById('syncStatus2');
    const fpsDisplay = document.getElementById('fpsDisplay');
    const latencyDisplay = document.getElementById('latencyDisplay');

    if (!this.params.running) {
      // 开始实验
      this.params.running = true;
      this.params.time = 0;
      this.params.angularVelocity = 0;
      this.params.periodCount = 0;
      this.params.periods = [];
      this.params.startTime = Date.now();
      this.params.maxAngularVelocity = 0;
      this.animationAngleHistory = [];
      this.animationPositionHistory = [];
      this.animationStartTime = Date.now();
      this.startBtn.textContent = '暂停实验';
      
      // 发送d0信号
      console.log('toggleStart中准备发送d0信号');
      if (typeof window.sendKey === 'function') {
        console.log('window.sendKey是一个函数');
        window.sendKey('d0');
        console.log('toggleStart中d0信号已发送');
      } else {
        console.error('window.sendKey不是一个函数:', typeof window.sendKey);
      }

      // 更新状态指示器
      if (runStatus && statusText) {
        runStatus.classList.remove('not-running');
        runStatus.classList.add('running');
        statusText.textContent = '正在运行';
      }

      // 更新同步状态
      if (syncStatus2) {
        syncStatus2.innerHTML = '🟢 正在运行';
        syncStatus2.className = 'status-value running';
      }

      // 高亮显示FPS和延迟
      if (fpsDisplay) fpsDisplay.classList.add('highlight');
      if (latencyDisplay) latencyDisplay.classList.add('highlight');

      this.updateSliders();
      this.simulate();
      window.currentExperiment = {
        initialAngle: this.params.angle,
        length: this.params.physicalLength,
        periods: [],
        startTime: Date.now(),
        gravity: null
      };
    } else {
      // 暂停实验
      this.params.running = false;
      this.startBtn.textContent = '继续实验';

      // 更新状态指示器
      if (runStatus && statusText) {
        runStatus.classList.remove('running');
        runStatus.classList.add('not-running');
        statusText.textContent = '未运行';
      }

      // 更新同步状态
      if (syncStatus2) {
        syncStatus2.innerHTML = '🟡 等待连接';
        syncStatus2.className = 'status-value waiting';
      }

      // 移除高亮
      if (fpsDisplay) fpsDisplay.classList.remove('highlight');
      if (latencyDisplay) latencyDisplay.classList.remove('highlight');
    }
  },

  /**
   * 单摆物理仿真主循环
   */
  simulate() {
    if (!this.params.running) return;
    const currentTime = Date.now();
    const deltaTime = Math.min((currentTime - (this.params.startTime + this.params.time * 1000)) / 1000, 0.1);
    this.params.time += deltaTime;
    this.timer.textContent = this.params.time.toFixed(2);
    const angleRad = this.params.angle * Math.PI / 180;
    const physicalLengthM = this.params.physicalLength / 100;
    const gravity = 9.8;
    const damping = 0.008;
    this.params.angularAcceleration = -(gravity * Math.sin(angleRad)) / physicalLengthM;
    this.params.angularAcceleration -= damping * this.params.angularVelocity;
    this.params.angularVelocity += this.params.angularAcceleration * deltaTime;
    this.params.angle += this.params.angularVelocity * deltaTime * 180 / Math.PI;
    // 记录动画数据
    const now = Date.now();
    const timeFromStart = (now - this.animationStartTime) / 1000;
    if (!window.animationAngleHistory) window.animationAngleHistory = [];
    if (!window.animationPositionHistory) window.animationPositionHistory = [];
    window.animationAngleHistory.push({ time: timeFromStart, angle: this.params.angle });
    window.animationPositionHistory.push({ time: timeFromStart, x: this.params.pivotX + this.params.physicalLength * this.params.displayScale * Math.sin(angleRad) - this.params.pivotX, y: this.params.pivotY + this.params.physicalLength * this.params.displayScale * Math.cos(angleRad) - this.params.pivotY });
    if (window.animationAngleHistory.length > 1000) window.animationAngleHistory.shift();
    if (window.animationPositionHistory.length > 1000) window.animationPositionHistory.shift();
    // 追踪最大角速度
    if (Math.abs(this.params.angularVelocity) > Math.abs(this.params.maxAngularVelocity)) {
      this.params.maxAngularVelocity = this.params.angularVelocity;
    }
    // 绘制
    this.draw();
    
    // 更新性能统计
    this.updatePerformanceStats();
    
    // 继续动画
    requestAnimationFrame(() => this.simulate());
  },

  /**
   * 绘制单摆
   */
  draw() {
    const now = performance.now();
    if (now - this.lastDrawTime < this.DRAW_INTERVAL) return;
    this.lastDrawTime = now;
    const ctx = this.ctx;
    const p = this.params;
    // 角度选择逻辑
    let angleToDraw = p.angle;
    if (!p.running && typeof this.cameraAngle === 'number') {
      angleToDraw = this.cameraAngle;
    }
    // 摆长选择逻辑
    let lengthToDraw = p.physicalLength;
    if (!p.running && typeof this.displayPhysicalLength === 'number') {
      lengthToDraw = this.displayPhysicalLength;
    }
    const angleRad = angleToDraw * Math.PI / 180;
    const displayLength = lengthToDraw * p.displayScale;
    const bobX = p.pivotX + displayLength * Math.sin(angleRad);
    const bobY = p.pivotY + displayLength * Math.cos(angleRad);
    ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    // 绘制背景
    this.drawBackground();
    // 绘制单摆支架
    ctx.fillStyle = 'rgba(70, 90, 140, 0.8)';
    ctx.fillRect(p.pivotX - 100, p.pivotY - 40, 200, 40);
    // 绘制刻度盘
    this.drawDial(angleRad);
    // 绘制摆线
    ctx.beginPath();
    ctx.moveTo(p.pivotX, p.pivotY);
    ctx.lineTo(bobX, bobY);
    ctx.strokeStyle = 'rgba(240, 248, 255, 0.9)';
    ctx.lineWidth = 3;
    ctx.stroke();
    // 绘制摆球
    this.drawBob(bobX, bobY);
    // 绘制角度标记
    if (!p.running) {
      ctx.font = '18px Arial';
      ctx.fillStyle = '#80deea';
      ctx.fillText(`当前角度: ${angleToDraw.toFixed(1)}°`, p.pivotX + 40, p.pivotY - 25);
    }
    // 绘制长度标记
    if (!p.running || Math.abs(angleToDraw) > 2) {
      ctx.beginPath();
      ctx.setLineDash([5, 5]);
      ctx.moveTo(p.pivotX, p.pivotY);
      ctx.lineTo(p.pivotX, p.pivotY + displayLength);
      ctx.strokeStyle = 'rgba(100, 180, 255, 0.6)';
      ctx.lineWidth = 1;
      ctx.stroke();
      ctx.setLineDash([]);
      ctx.font = '16px Arial';
      ctx.fillStyle = '#80deea';
      ctx.fillText(`摆长: ${lengthToDraw.toFixed(1)} cm`, p.pivotX + 15, p.pivotY + displayLength / 2);
    }
    // 绘制摆轴位置信息
    if (!p.running) {
      ctx.font = '14px Arial';
      ctx.fillStyle = '#ffcc80';
      ctx.fillText(`摆轴: (${p.pivotX.toFixed(0)}, ${p.pivotY.toFixed(0)})`, 10, this.canvas.height - 30);
    }
  },

  /**
   * 绘制背景
   */
  drawBackground() {
    const ctx = this.ctx;
    const w = this.canvas.width, h = this.canvas.height;
    const gradient = ctx.createLinearGradient(0, 0, w, h);
    gradient.addColorStop(0, 'rgba(20, 30, 80, 0.4)');
    gradient.addColorStop(1, 'rgba(10, 20, 50, 0.8)');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, w, h);
    ctx.strokeStyle = 'rgba(100, 140, 255, 0.1)';
    ctx.lineWidth = 1;
    for (let x = 0; x < w; x += 60) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
    }
    for (let y = 0; y < h; y += 60) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
    }
  },

  /**
   * 绘制刻度盘
   */
  drawDial(angleRad) {
    const ctx = this.ctx;
    const p = this.params;
    const dialRadius = 80;
    ctx.beginPath();
    ctx.arc(p.pivotX, p.pivotY, dialRadius, 0, Math.PI * 2);
    ctx.strokeStyle = 'rgba(100, 180, 255, 0.4)';
    ctx.lineWidth = 2;
    ctx.stroke();
    for (let i = -90; i <= 90; i += 10) {
      const rad = i * Math.PI / 180;
      const size = (i % 30 === 0) ? 12 : (i % 15 === 0) ? 8 : 5;
      const innerX = p.pivotX + (dialRadius - size) * Math.sin(rad);
      const innerY = p.pivotY + (dialRadius - size) * Math.cos(rad);
      const outerX = p.pivotX + dialRadius * Math.sin(rad);
      const outerY = p.pivotY + dialRadius * Math.cos(rad);
      ctx.beginPath();
      ctx.moveTo(innerX, innerY);
      ctx.lineTo(outerX, outerY);
      ctx.strokeStyle = (i === 0) ? '#4fc3f7' : 'rgba(100, 180, 255, 0.5)';
      ctx.lineWidth = (i % 30 === 0) ? 2 : 1;
      ctx.stroke();
      if (i % 30 === 0 && i !== 0) {
        const textX = p.pivotX + (dialRadius + 15) * Math.sin(rad);
        const textY = p.pivotY + (dialRadius + 15) * Math.cos(rad);
        ctx.font = '14px Arial';
        ctx.fillStyle = '#90caf9';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(Math.abs(i) + '°', textX, textY);
      }
    }
  },

  /**
   * 绘制摆球
   */
  drawBob(bobX, bobY) {
    const ctx = this.ctx;
    const p = this.params;
    const gradient = ctx.createRadialGradient(
      bobX - 10, bobY - 10, p.bobRadius * 0.1,
      bobX, bobY, p.bobRadius
    );
    gradient.addColorStop(0, '#ffcc80');
    gradient.addColorStop(1, '#ffa726');
    ctx.beginPath();
    ctx.arc(bobX, bobY, p.bobRadius, 0, Math.PI * 2);
    ctx.fillStyle = gradient;
    ctx.fill();
    ctx.beginPath();
    ctx.arc(bobX - p.bobRadius/3, bobY - p.bobRadius/3, p.bobRadius/4, 0, Math.PI * 2);
    ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
    ctx.fill();
    ctx.font = '12px Arial';
    ctx.fillStyle = '#fff';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('P', bobX, bobY);
  },

  /**
   * 重置实验
   */
  reset() {
    const currentPhysicalLength = this.params.physicalLength;
    this.params = {
      physicalLength: currentPhysicalLength,
      displayScale: 5,
      angle: 5,
      pivotX: this.canvas.width / 2,
      pivotY: 180,
      bobRadius: 24,
      angularVelocity: 0,
      angularAcceleration: 0,
      time: 0,
      running: false,
      periodCount: 0,
      lastPeriodTime: 0,
      periods: [],
      periodTiming: false,
      startTime: 0,
      maxAngularVelocity: 0,
      initialEnergy: 0,
      currentEnergy: 0,
      lastHiddenTime: null
    };

    // 重置同步标记
    this.isSyncingFromCamera = false;

    // 更新状态指示器
    const runStatus = document.getElementById('runStatus');
    const statusText = runStatus?.querySelector('.status-text');
    if (runStatus && statusText) {
      runStatus.classList.remove('running');
      runStatus.classList.add('not-running');
      statusText.textContent = '未运行';
    }

    // 更新同步状态
    const syncStatus2 = document.getElementById('syncStatus2');
    const fpsDisplay = document.getElementById('fpsDisplay');
    const latencyDisplay = document.getElementById('latencyDisplay');
    
    if (syncStatus2) {
      syncStatus2.innerHTML = '🟡 等待连接';
      syncStatus2.className = 'status-value waiting';
    }
    
    // 移除高亮
    if (fpsDisplay) {
      fpsDisplay.classList.remove('highlight');
      fpsDisplay.classList.remove('running', 'waiting', 'error');
      fpsDisplay.textContent = '0';
    }
    
    if (latencyDisplay) {
      latencyDisplay.classList.remove('highlight');
      latencyDisplay.classList.remove('running', 'waiting', 'error');
      latencyDisplay.textContent = '0ms';
    }

    // 清空历史数据
    this.animationAngleHistory = [];
    this.animationPositionHistory = [];
    this.animationStartTime = performance.now();
    this.startBtn.textContent = '开始实验';
    this.timer.textContent = '0.00';
    this.lengthSlider.value = this.params.physicalLength;
    this.updateSliders();
    this.draw();
    // ...实验数据保存逻辑略...
    this.fetchAndSyncFromCamera();
    // reset() 里清空window上的数组
    if (window.animationAngleHistory) window.animationAngleHistory = [];
    if (window.animationPositionHistory) window.animationPositionHistory = [];
    // --- 按重置实验保存实验数据 ---
    if (!window.experimentGroups) window.experimentGroups = [];
    if (window.currentExperiment) {
      // 立即push当前实验数据（周期可能为空）
      window.experimentGroups.push(window.currentExperiment);
      // 读取serial周期数据
      window.currentExperiment.periods = [];
      try {
        fetch('http://127.0.0.1:5000/get_serial_log')
          .then(r => r.json())
          .then(data => {
            if (data.log && Array.isArray(data.log)) {
              let total = 0;
              window.currentExperiment.periods = [];
              data.log.forEach((line, idx) => {
                // 支持 [接收]11.000000,0.967675,10.635322 格式
                const match = line.match(/(\d+)[,，]([0-9.]+)[,，]([0-9.]+)/);
                if (match) {
                  const index = parseInt(match[1]);
                  const duration = parseFloat(match[2]);
                  const totalTime = parseFloat(match[3]);
                  window.currentExperiment.periods.push({
                    index,
                    duration,
                    total: totalTime
                  });
                }
              });
            }
            // 计算重力加速度
            const periods = window.currentExperiment.periods;
            const length = window.currentExperiment.length;
            if (periods.length > 0) {
              const avgT = periods.reduce((a, b) => a + b.duration, 0) / periods.length;
              const L = length / 100;
              window.currentExperiment.gravity = 4 * Math.PI * Math.PI * L / (avgT * avgT);
            }
            // 更新experimentGroups最后一组
            if (window.experimentGroups && window.experimentGroups.length > 0) {
              const last = window.experimentGroups[window.experimentGroups.length - 1];
              last.periods = window.currentExperiment.periods;
              last.gravity = window.currentExperiment.gravity;
            }
            window.currentExperiment = null;
          });
      } catch (e) {
        // 若fetch失败，直接保存空周期
        window.currentExperiment.periods = [];
        // 不再push，已在前面push
        window.currentExperiment = null;
      }
    }
  },

  /**
   * 摄像头同步性能监控更新
   */
  updatePerformanceStats() {
    this.frameCount++;
    const now = performance.now();
    if (now - this.lastFpsTime >= 1000) {
      this.currentFps = this.frameCount;
      this.frameCount = 0;
      this.lastFpsTime = now;
    }
    
    // 更新FPS和延迟显示
    const fpsDisplay = document.getElementById('fpsDisplay');
    const latencyDisplay = document.getElementById('latencyDisplay');
    
    if (fpsDisplay) {
      fpsDisplay.textContent = this.currentFps;
      // 根据FPS值设置不同的样式
      fpsDisplay.className = 'status-value';
      if (this.currentFps >= 25) {
        fpsDisplay.classList.add('running');
      } else if (this.currentFps >= 15) {
        fpsDisplay.classList.add('waiting');
      } else {
        fpsDisplay.classList.add('error');
      }
    }
    
    if (latencyDisplay) {
      latencyDisplay.textContent = `${this.syncLatency.toFixed(0)}ms`;
      // 根据延迟值设置不同的样式
      latencyDisplay.className = 'status-value';
      if (this.syncLatency <= 50) {
        latencyDisplay.classList.add('running');
      } else if (this.syncLatency <= 150) {
        latencyDisplay.classList.add('waiting');
      } else {
        latencyDisplay.classList.add('error');
      }
    }
  },

  /**
   * 从后端获取摄像头识别的小球角度，驱动动画实时同步（只同步角度）
   */
  fetchAndSyncFromCamera() {
    // 如果已经在获取数据或不应该获取数据，则返回
    if (this.isFetchingCameraData || !this.params || this.params.running) return;
    
    // 设置标记，表示正在获取数据
    this.isFetchingCameraData = true;
    
    fetch('http://127.0.0.1:5000/get_pendulum_state')
      .then(r => {
        if (!r.ok) {
          throw new Error(`HTTP错误: ${r.status}`);
        }
        return r.json();
      })
      .then(data => {
        // 优先用camera_angle
        if (typeof data.camera_angle === 'number') {
          this.cameraAngle = data.camera_angle;
        } else if (typeof data.angle === 'number') {
          this.cameraAngle = data.angle;
        }
        // 只在实验未运行时用摄像头角度更新动画
        if (!this.params.running) {
          this.params.angle = this.cameraAngle;
        }
        // 不再从摄像头同步摆长数据，摆长只通过单片机信号更新
        
        // 记录动画历史数据，在 fetchAndSyncFromCamera 里同步记录历史数据，让曲线有数据。
        const now = performance.now();
        const timeFromStart = (now - this.animationStartTime) / 1000;
        if (!window.animationAngleHistory) window.animationAngleHistory = [];
        if (!window.animationPositionHistory) window.animationPositionHistory = [];
        window.animationAngleHistory.push({ time: timeFromStart, angle: this.params.angle });
        // 计算小球位置
        const p = this.params;
        const angleRad = p.angle * Math.PI / 180;
        const displayLength = p.physicalLength * p.displayScale;
        const bobX = p.pivotX + displayLength * Math.sin(angleRad);
        const bobY = p.pivotY + displayLength * Math.cos(angleRad);
        window.animationPositionHistory.push({ time: timeFromStart, x: bobX - p.pivotX, y: bobY - p.pivotY });
        // 限制历史数据长度
        if (window.animationAngleHistory.length > 1000) window.animationAngleHistory.shift();
        if (window.animationPositionHistory.length > 1000) window.animationPositionHistory.shift();
        // 同步更新滑块显示
        this.updateSliders && this.updateSliders();
        this.draw && this.draw();
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
          syncStatus.textContent = '✅ 已同步摄像头数据';
          syncStatus.style.color = '#4caf50';
        }
        
        // 更新同步状态2
        const syncStatus2 = document.getElementById('syncStatus2');
        if (syncStatus2) {
          syncStatus2.innerHTML = '✅ 已同步';
          syncStatus2.className = 'status-value running';
        }
        
        // 更新性能统计
        this.frameCount++;
        this.syncLatency = now - (data.timestamp || now);
        this.updatePerformanceStats();

        // 请求完成，重置标记
        this.isFetchingCameraData = false;
      })
      .catch(error => {
        console.warn('获取摄像头数据失败:', error);
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
          syncStatus.textContent = '⚠️ 摄像头数据同步失败';
          syncStatus.style.color = '#f44336';
        }
        
        // 更新同步状态2
        const syncStatus2 = document.getElementById('syncStatus2');
        if (syncStatus2) {
          syncStatus2.innerHTML = '❌ 同步失败';
          syncStatus2.className = 'status-value error';
        }
        
        // 更新性能统计
        this.updatePerformanceStats();
        
        // 即使请求失败，也要重置标记
        this.isFetchingCameraData = false;
      });
  },

  /**
   * 摄像头同步动画
   */
  syncPendulumAnimation(data) {
    try {
      const now = performance.now();
      if (now - this.lastUpdateTime < this.MIN_UPDATE_INTERVAL) {
        return;
      }
      this.lastUpdateTime = now;
      // 计算同步延迟
      this.syncLatency = now - (data.timestamp || now);
      // 如果实验正在运行，则不进行摄像头同步
      if (this.params.running) {
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
          syncStatus.textContent = '🔴 实验运行中 - 独立模拟模式';
          syncStatus.style.color = '#f44336';
        }
        this.updatePerformanceStats();
        return;
      }
      // 如果摄像头检测到有效数据，则同步动画
      if (data && data.angle !== 0) {
        this.isSyncingFromCamera = true;
        // 计算运动预测
        const currentTime = now / 1000;
        const targetAngle = data.angle;
        // 不再同步摆长，摆长只通过单片机信号更新
        
        // 更新历史数据
        this.angleHistory.push(targetAngle);
        // 不再记录摆长历史数据
        this.timeHistory.push(currentTime);
        if (this.angleHistory.length > this.HISTORY_SIZE) {
          this.angleHistory.shift();
          // 不再处理摆长历史数据
          this.timeHistory.shift();
        }
        // 计算更准确的角速度和角加速度
        let angleVelocity = 0, angleAcceleration = 0;
        if (this.angleHistory.length >= 3) {
          try {
            const dt1 = this.timeHistory[this.timeHistory.length - 1] - this.timeHistory[this.timeHistory.length - 2];
            const dt2 = this.timeHistory[this.timeHistory.length - 2] - this.timeHistory[this.timeHistory.length - 3];
            if (dt1 > 0 && dt2 > 0) {
              const v1 = (this.angleHistory[this.angleHistory.length - 1] - this.angleHistory[this.angleHistory.length - 2]) / dt1;
              const v2 = (this.angleHistory[this.angleHistory.length - 2] - this.angleHistory[this.angleHistory.length - 3]) / dt2;
              angleVelocity = v1;
              angleAcceleration = (v1 - v2) / ((dt1 + dt2) / 2);
            }
          } catch (e) {
            console.warn('计算角速度/加速度错误:', e);
          }
        }
        // 预测和平滑
        const smoothingFactor = window.currentSmoothingFactor || 0.2;
        const predictionTime = window.currentPredictionTime || 0.0;
        const predictedAngle = targetAngle + angleVelocity * predictionTime + 0.5 * angleAcceleration * predictionTime * predictionTime;
        if (Math.abs(this.params.angle - predictedAngle) > 0.01) {
          this.params.angle = this.params.angle * (1 - smoothingFactor) + predictedAngle * smoothingFactor;
        } else {
          this.params.angle = predictedAngle;
        }
        
        // 不再处理摆长的平滑更新
        
        // 更新滑块显示（不触发事件）
        if (!this.isSyncingFromCamera) {
          // 不再更新摆长滑块
          if (this.angleSlider) this.angleSlider.value = this.params.angle;
          this.updateSliders();
        }
        // 强制重绘
        this.draw();
        // 更新实验参数设置中的摆长和角度显示
        if (this.lengthValue) this.lengthValue.textContent = this.params.physicalLength.toFixed(1);
        if (this.angleValue) this.angleValue.textContent = this.params.angle.toFixed(1);
        // 更新摄像头状态显示
        const pendulumState = document.getElementById('pendulumState');
        if (pendulumState) {
          let stateText = `角度: ${data.angle.toFixed(2)}°    摆长: ${this.params.physicalLength.toFixed(2)} cm (单片机控制)`;
          if (data.reference_angle !== undefined && data.reference_angle !== 0) {
            stateText += `    参考: ${data.reference_angle.toFixed(2)}°`;
          }
          pendulumState.textContent = stateText;
        }
        // 更新同步状态指示器
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
          syncStatus.textContent = '✅ 已同步摄像头数据';
          syncStatus.style.color = '#4caf50';
        }
        
        // 更新同步状态2
        const syncStatus2 = document.getElementById('syncStatus2');
        if (syncStatus2) {
          syncStatus2.innerHTML = '✅ 已同步';
          syncStatus2.className = 'status-value running';
        }
        
        this.isSyncingFromCamera = false;
      } else {
        // 更新同步状态指示器
        const syncStatus = document.getElementById('syncStatus');
        if (syncStatus) {
          syncStatus.textContent = '⏳ 等待有效数据...';
          syncStatus.style.color = '#ff9800';
        }
        
        // 更新同步状态2
        const syncStatus2 = document.getElementById('syncStatus2');
        if (syncStatus2) {
          syncStatus2.innerHTML = '⏳ 等待数据';
          syncStatus2.className = 'status-value waiting';
        }
      }
      this.updatePerformanceStats();
    } catch (error) {
      console.warn('同步动画处理错误:', error);
      this.isSyncingFromCamera = false;
    }
  }
}; 